//
// Created by LiuYou on 2021/7/3.
//

#include "Relayer.hpp"
#include <cstdlib>
#include <fcntl.h>
#include <cstdio>
#include <unistd.h>
#include <cerrno>
#include <pthread.h>
#include <cstring>


#define BUF_SIZE        1024


enum {
    STATE_R,
    STATE_W,
    STATE_EX,
    STATE_T,
};


struct RelayerFSM {
    int state;
    int sfd;
    int dfd;
    char buf[BUF_SIZE]{};
    int len;
    int pos;
    char* err;
    int64_t count;
};


struct RelayerJob {
    int fd1;
    int fd2;
    int jobState;
    struct RelayerFSM relayerFsm12;
    struct RelayerFSM relayerFsm21;
    int fd1Save;
    int fd2Save;
//    struct timeval start,end;

};


static struct RelayerJob* relayerJob[REL_JOB_MAX];
static pthread_mutex_t relayJobMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t initOnce = PTHREAD_ONCE_INIT;


static void fsmDriver(struct RelayerFSM* fsm) {
    switch (fsm->state) {
        case STATE_R:
            fsm->len = read(fsm->sfd, fsm->buf, BUF_SIZE);
            if (fsm->len == 0) {
                fsm->state = STATE_T;
            } else if (fsm->len < 0) {
                if (errno == EAGAIN) {
                    fsm->state = STATE_R;
                } else {
                    fsm->err = "read()";
                    fsm->state = STATE_EX;
                }
            } else {
                fsm->pos = 0;
                fsm->state = STATE_W;
            }
            break;
        case STATE_W: {
            ssize_t ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
            if (ret < 0) {
                if (errno == EAGAIN) {
                    fsm->state = STATE_W;
                } else {
                    fsm->err = "write()";
                    fsm->state = STATE_EX;
                }
            } else {
                fsm->pos += ret;
                fsm->len -= ret;
                if (fsm->len == 0) {
                    fsm->state = STATE_R;
                } else {
                    fsm->state = STATE_W;
                }
            }
        }
            break;
        case STATE_EX:
            std::perror(fsm->err);
            fsm->state = STATE_T;
            break;
        case STATE_T:
            // terminate
            break;
        default:
            abort();
            break;
    }

}


static void* threadRelayer(void* pVoid) {

    while (true) {
        // TODO 注意: 因为要访问 relayerJob[]数组, 所以要加锁
        pthread_mutex_lock(&relayJobMutex);
        for (int i = 0; i < REL_JOB_MAX; ++i) {
            if (relayerJob[i] != nullptr) {
                if (relayerJob[i]->jobState == STATE_RUNNING) {

                    fsmDriver(&relayerJob[i]->relayerFsm12);
                    fsmDriver(&relayerJob[i]->relayerFsm21);

                    if (relayerJob[i]->relayerFsm12.state == STATE_T &&
                        relayerJob[i]->relayerFsm21.state == STATE_T) {
                        relayerJob[i]->jobState = STATE_OVER;
                    }
                }
            }
        }
        pthread_mutex_unlock(&relayJobMutex);
    }

    pthread_exit(nullptr);
}


// moduleUnload


static void moduleLoad() {
    pthread_t tidRelayer;
    int err = pthread_create(&tidRelayer, nullptr, threadRelayer, nullptr);
    if (err) {
        std::fprintf(stderr, "pthread_create(): %s\n", std::strerror(err));
        std::exit(1);
    }
}


static int getFreePosUnlocked() {
    for (int i = 0; i < REL_JOB_MAX; ++i) {
        if (relayerJob[i] == nullptr) {
            return i;
        }
    }
    return -1;
}


int relAddJob(int fd1, int fd2) {
    pthread_once(&initOnce, moduleLoad);
    struct RelayerJob* me = static_cast<RelayerJob*>(malloc(sizeof(RelayerJob)));
    if (me == nullptr) {
        return -ENOMEM;
    }
    me->fd1 = fd1;
    me->fd2 = fd2;
    me->jobState = STATE_RUNNING;

    me->fd1Save = fcntl(me->fd1, F_GETFL);
    fcntl(me->fd1, F_SETFL, me->fd1Save | O_NONBLOCK);
    me->fd2Save = fcntl(me->fd2, F_GETFL);
    fcntl(me->fd2, F_SETFL, me->fd2Save | O_NONBLOCK);

    me->relayerFsm12.sfd = me->fd1;
    me->relayerFsm12.dfd = me->fd2;
    me->relayerFsm12.state = STATE_R;

    me->relayerFsm21.sfd = me->fd2;
    me->relayerFsm21.dfd = me->fd1;
    me->relayerFsm21.state = STATE_R;

    pthread_mutex_lock(&relayJobMutex);
    int pos = getFreePosUnlocked();
    if (pos < 0) {
        pthread_mutex_unlock(&relayJobMutex);
        fcntl(me->fd1, F_SETFL, me->fd1Save);
        fcntl(me->fd2, F_SETFL, me->fd2Save);
        free(me);
        return -ENOSPC;
    }

    relayerJob[pos] = me;

    pthread_mutex_unlock(&relayJobMutex);

    return pos;
}

int relCanCelJob(int id) {
    return 0;
}

int relWaitJob(int id, struct RelayerState*) {
    return 0;
}

int relStateJob(int id, struct RelayerState*) {
    return 0;
}
